package com.ffws.media.service.impl;

import com.ffws.base.exception.FFWSException;
import com.ffws.base.model.RestResponse;
import com.ffws.media.mapper.MediaFilesMapper;
import com.ffws.media.model.dto.MediaFilesDto;
import com.ffws.media.model.po.MediaFiles;
import com.ffws.media.service.MediaFileService;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Author JC
 * @description TODO
 * @Date 2023-9-4 16:43
 * @Version 1.0
 */
@Service
@Slf4j
public class MediaFileServiceImpl implements MediaFileService {

    @Autowired
    MinioClient minioClient;

    @Resource
    MediaFilesMapper mediaFilesMapper;

    // 存储普通文件
    @Value("${minio.bucket.files}")
    private String bucket_mediafiles;

    // 存储视频
    @Value("${minio.bucket.videofiles}")
    private String bucket_video;

    // Minio文件访问路径
    @Value("${minio.bucket.miniopath}")
    private String MinioPath;

    @Autowired
    MediaFileService currentProxy;

    @Override
    public RestResponse<Boolean> checkFile(MultipartFile file) {
        // 创建一个临时文件
        File tempFile = null;
        try {
            tempFile = File.createTempFile("CheckFile", ".temp");
            file.transferTo(tempFile);
        } catch (IOException e) {
            log.error("检查文件过程中,创建临时文件失败!");
            e.printStackTrace();
            return RestResponse.validfail("检查文件过程中,创建临时文件失败");
        }
        String fileMd5 = getFileMd5(tempFile);
        // 先查询数据库
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles != null) {
            // 桶
            String bucket = mediaFiles.getBucket();
            // objectname
            String filePath = mediaFiles.getFilePath();
            // 如果数据库存在再查询minio
            GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                    .bucket(bucket) // 桶
                    .object(filePath)
                    .build();// 对象名
            // 查询远程服务器获取到一个流对象
            try {
                FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
                if (inputStream != null) {
                    // 文件已存在
                    return RestResponse.success(true);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 文件不存在
        return RestResponse.success(false);
    }

    @Override
    public RestResponse<MediaFilesDto> uploadFile(MultipartFile file) {
        // 文件名
        String filename = file.getOriginalFilename();
        // 先得到扩展名
        String extension = filename.substring(filename.lastIndexOf("."));
        // 得到mimeType
        String mimeType = getMimeType(extension);
        // 子目录
        String defaultFolderPath = getDefaultFolderPath();
        // 创建一个临时文件
        File tempFile = null;
        try {
            tempFile = File.createTempFile("UploadFile", ".temp");
            file.transferTo(tempFile);
        } catch (IOException e) {
            log.error("上传文件过程中,创建临时文件失败!");
            e.printStackTrace();
            return RestResponse.validfail("上传文件过程中,创建临时文件失败");
        }
        // 文件的md5值
        String fileMd5 = getFileMd5(tempFile);
        MediaFiles mediaFilesForNotNull = mediaFilesMapper.selectById(fileMd5);
        if (mediaFilesForNotNull != null) {
            // 该文件上传过
            MediaFilesDto mediaFilesDto = new MediaFilesDto();
            BeanUtils.copyProperties(mediaFilesForNotNull,mediaFilesDto);
            mediaFilesDto.setFinalPath(MinioPath + mediaFilesDto.getUrl());
            return RestResponse.success(mediaFilesDto);
        }
        String objectName = defaultFolderPath + fileMd5 + extension;
        // 上传文件到minio
        boolean result = addMediaFilesToMinIO(tempFile.getAbsolutePath(), mimeType, bucket_mediafiles, objectName);
        if (!result) {
            FFWSException.cast("上传文件失败");
        }
        // 入库文件信息
        MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(fileMd5, bucket_mediafiles, objectName);
        if (mediaFiles == null) {
            FFWSException.cast("文件上传后保存信息失败");
        }
        MediaFilesDto mediaFilesDto = new MediaFilesDto();
        BeanUtils.copyProperties(mediaFiles,mediaFilesDto);
        mediaFilesDto.setFinalPath(MinioPath + mediaFilesDto.getUrl());
        return RestResponse.success(mediaFilesDto);
    }

    /**
     * 将文件信息保存到数据库
     * @param fileMd5 文件md5值
     * @param bucket 桶
     * @param objectName 对象名称
     * @return com.ffws.media.model.po.MediaFiles
     */
    @Transactional
    @Override
    public MediaFiles addMediaFilesToDb(String fileMd5, String bucket, String objectName) {
        // 将文件信息保存到数据库
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles == null) {
            mediaFiles = new MediaFiles();
            // 设置md5
            mediaFiles.setId(fileMd5);
            // 文件名称
            mediaFiles.setName(objectName.substring(objectName.lastIndexOf("/") + 1));
            // 桶
            mediaFiles.setBucket(bucket);
            // file_path
            mediaFiles.setFilePath(objectName);
            // url
            mediaFiles.setUrl("/" + bucket + "/" + objectName);
            // 类型:图片
            mediaFiles.setType(0);
            // 插入数据库
            int insert = mediaFilesMapper.insert(mediaFiles);
            if (insert <= 0) {
                log.error("向数据库保存文件信息失败,bucket:{},objectName:{}", bucket, objectName);
                return null;
            }
            return mediaFiles;
        }
        return mediaFiles;
    }

    /**
     * 将文件上传到minio
     *
     * @param localFilePath 文件本地路径
     * @param mimeType      媒体类型
     * @param bucket        桶
     * @param objectName    对象名
     * @return
     */
    public boolean addMediaFilesToMinIO(String localFilePath, String mimeType, String bucket, String objectName) {
        // 上传文件的参数信息
        UploadObjectArgs uploadObjectArgs = null;
        try {
            uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket) // 桶
                    .filename(localFilePath) // 指定本地文件路径
                    .object(objectName) // 对象名 放在子目录下
                    .contentType(mimeType) // 设置媒体文件类型
                    .build();
            // 上传文件
            minioClient.uploadObject(uploadObjectArgs);
            log.info("上传文件到minio成功,bucket:{},objectName:{}", bucket, objectName);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件出错,bucket:{},objectName:{},错误信息:{}", bucket, objectName, e.getMessage());
        }
        return false;
    }


    /**
     * 获取文件默认存储目录路径 年/月/日
     * @return 文件默认存储目录路径 年/月/日
     */
    private String getDefaultFolderPath() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(new Date()).replace("-", "/") + "/";
    }

    /**
     * 根据扩展名获取mimeType
     * @param extension 扩展名
     * @return mimeType
     */
    private String getMimeType(String extension) {
        if (extension == null) {
            extension = "";
        }
        // 通过扩展名得到媒体资源的类型 mimeType
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE; // 通用mimeType,字节流
        if (extensionMatch != null) {
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }

    /**
     * 获取文件的md5
     * @param file 文件
     * @return 文件的md5
     */
    private String getFileMd5(File file) {
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            return DigestUtils.md5Hex(fileInputStream);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

}
